import {
  filterConfig,
  getManagerConfig,
  mergeChildConfig,
} from '../../../config';
import type { RenovateConfig } from '../../../config/types';
import { logger } from '../../../logger';
import { getDefaultConfig } from '../../../modules/datasource';
import { get } from '../../../modules/manager';
import type { PackageFile } from '../../../modules/manager/types';
import { detectSemanticCommits } from '../../../util/git/semantic';
import { applyPackageRules } from '../../../util/package-rules';
import { regEx } from '../../../util/regex';
import * as template from '../../../util/template';
import { parseUrl } from '../../../util/url';
import type { BranchUpgradeConfig } from '../../types';
import { generateBranchName } from './branch-name';

const upper = (str: string): string =>
  str.charAt(0).toUpperCase() + str.substring(1);

export function sanitizeDepName(depName: string): string {
  return depName
    .replace('@types/', '')
    .replace('@', '')
    .replace(regEx(/\//g), '-')
    .replace(regEx(/\s+/g), '-')
    .replace(regEx(/:/g), '-')
    .replace(regEx(/-+/), '-')
    .toLowerCase();
}

export function applyUpdateConfig(input: BranchUpgradeConfig): any {
  const updateConfig = { ...input };
  delete updateConfig.packageRules;
  // TODO: Remove next line once #8075 is complete
  updateConfig.depNameSanitized = updateConfig.depName
    ? sanitizeDepName(updateConfig.depName)
    : undefined;
  updateConfig.newNameSanitized = updateConfig.newName
    ? sanitizeDepName(updateConfig.newName)
    : undefined;
  if (updateConfig.sourceUrl) {
    const parsedSourceUrl = parseUrl(updateConfig.sourceUrl);
    if (parsedSourceUrl?.pathname) {
      updateConfig.sourceRepoSlug = parsedSourceUrl.pathname
        .replace(regEx(/^\//), '') // remove leading slash
        .replace(regEx(/\//g), '-') // change slashes to hyphens
        .replace(regEx(/-+/g), '-'); // remove multiple hyphens
      updateConfig.sourceRepo = parsedSourceUrl.pathname.replace(
        regEx(/^\//),
        '',
      ); // remove leading slash
      updateConfig.sourceRepoOrg = updateConfig.sourceRepo.replace(
        regEx(/\/.*/g),
        '',
      ); // remove everything after first slash
      updateConfig.sourceRepoName = updateConfig.sourceRepo.replace(
        regEx(/.*\//g),
        '',
      ); // remove everything up to the last slash
    }
  }
  if (updateConfig.sourceDirectory) {
    updateConfig.sourceDirectory = template.compile(
      updateConfig.sourceDirectory,
      updateConfig,
    );
  }
  generateBranchName(updateConfig);
  return updateConfig;
}

export async function flattenUpdates(
  config: RenovateConfig,
  packageFiles: Record<string, PackageFile[]>,
): Promise<BranchUpgradeConfig[]> {
  const updates = [];
  const updateTypes = [
    'major',
    'minor',
    'patch',
    'pin',
    'digest',
    'lockFileMaintenance',
    'replacement',
  ];
  for (const [manager, files] of Object.entries(packageFiles)) {
    const managerConfig = getManagerConfig(config, manager);
    for (const packageFile of files) {
      const packageFileConfig: BranchUpgradeConfig = mergeChildConfig(
        managerConfig,
        packageFile,
      ) as never;
      const packagePath = packageFile.packageFile?.split('/');
      if (packagePath.length > 0) {
        packagePath.splice(-1, 1);
      }
      if (packagePath.length > 0) {
        packageFileConfig.parentDir = packagePath[packagePath.length - 1];
        packageFileConfig.packageFileDir = packagePath.join('/');
      } else {
        packageFileConfig.parentDir = '';
        packageFileConfig.packageFileDir = '';
      }
      let depIndex = 0;
      for (const dep of packageFile.deps) {
        if (dep.updates!.length) {
          const depConfig = mergeChildConfig(packageFileConfig, dep);
          // @ts-expect-error -- not easily typed
          delete depConfig.deps;
          depConfig.depIndex = depIndex; // used for autoreplace
          for (const update of dep.updates!) {
            let updateConfig = mergeChildConfig(depConfig, update);
            delete updateConfig.updates;
            if (updateConfig.updateType) {
              // @ts-expect-error -- not easily typed
              updateConfig[`is${upper(updateConfig.updateType)}`] = true;
            }
            if (updateConfig.updateTypes) {
              updateConfig.updateTypes.forEach((updateType: string) => {
                // @ts-expect-error -- not easily typed
                updateConfig[`is${upper(updateType)}`] = true;
              });
            }
            // apply config from datasource
            const datasourceConfig = await getDefaultConfig(
              depConfig.datasource!,
            );
            updateConfig = mergeChildConfig(updateConfig, datasourceConfig);
            updateConfig = await applyPackageRules(
              updateConfig,
              'datasource-merge',
            );
            // apply major/minor/patch/pin/digest
            updateConfig = mergeChildConfig(
              updateConfig,
              // @ts-expect-error -- not easily typed
              updateConfig[updateConfig.updateType],
            );
            for (const updateType of updateTypes) {
              // @ts-expect-error -- not easily typed
              delete updateConfig[updateType];
            }
            // Apply again in case any were added by the updateType config
            updateConfig = await applyPackageRules(
              updateConfig,
              'update-type-merge',
            );
            updateConfig = applyUpdateConfig(updateConfig);
            updateConfig.baseDeps = packageFile.deps;
            update.branchName = updateConfig.branchName;
            updates.push(updateConfig);
          }
        }
        depIndex += 1;
      }
      if (
        get(manager, 'supportsLockFileMaintenance') &&
        packageFileConfig.lockFileMaintenance!.enabled
      ) {
        // Apply lockFileMaintenance config before packageRules
        let lockFileConfig = mergeChildConfig(
          packageFileConfig,
          packageFileConfig.lockFileMaintenance!,
        );
        lockFileConfig.updateType = 'lockFileMaintenance';
        lockFileConfig.isLockFileMaintenance = true;
        lockFileConfig = await applyPackageRules(
          lockFileConfig,
          'lock-file-maintenance-merge',
        );
        // Apply lockFileMaintenance and packageRules again
        lockFileConfig = mergeChildConfig(
          lockFileConfig,
          lockFileConfig.lockFileMaintenance!,
        );
        lockFileConfig = await applyPackageRules(
          lockFileConfig,
          'lock-file-maintenance-merge-2',
        );
        // Remove unnecessary objects
        for (const updateType of updateTypes) {
          // @ts-expect-error -- not easily typed
          delete lockFileConfig[updateType];
        }
        delete lockFileConfig.packageRules;
        // @ts-expect-error -- not easily typed
        delete lockFileConfig.deps;
        generateBranchName(lockFileConfig);
        updates.push(lockFileConfig);
      }
      if (get(manager, 'updateLockedDependency')) {
        for (const lockFile of packageFileConfig.lockFiles ?? []) {
          const lockfileRemediations = config.remediations as Record<
            string,
            Record<string, any>[]
          >;
          const remediations = lockfileRemediations?.[lockFile];
          if (remediations) {
            for (const remediation of remediations) {
              let updateConfig = mergeChildConfig(
                packageFileConfig,
                remediation,
              );
              updateConfig = mergeChildConfig(
                updateConfig,
                config.vulnerabilityAlerts,
              );
              delete updateConfig.vulnerabilityAlerts;
              updateConfig.isVulnerabilityAlert = true;
              updateConfig.isRemediation = true;
              updateConfig.lockFile = lockFile;
              updateConfig.currentValue = updateConfig.currentVersion;
              updateConfig.newValue = updateConfig.newVersion;
              updateConfig = applyUpdateConfig(updateConfig);
              updateConfig.enabled = true;
              updates.push(updateConfig);
            }
          }
        }
      }
    }
  }
  if (config.semanticCommits === 'auto') {
    const semanticCommits = await detectSemanticCommits();
    for (const update of updates) {
      update.semanticCommits = semanticCommits;
    }
  }
  const filteredUpdates = updates
    .filter((update) => update.enabled !== false)
    .map(({ vulnerabilityAlerts, ...update }) => update)
    .map((update) => filterConfig(update, 'branch'));
  if (filteredUpdates.length < updates.length) {
    logger.debug(
      `Filtered out ${updates.length - filteredUpdates.length} disabled update(s). ${filteredUpdates.length} update(s) remaining.`,
    );
  }
  return filteredUpdates;
}
